﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
 
namespace Organized
{
    // Post processor that applies a Gaussian blur to an image or the frame buffer
    public class DepthOfField : PostProcessor
    {

  
        //visual settings
        public float dofStart = 100f;
        public float dofLength = 650f;

        //The camera object to get view matrixes
        Camera camera;

        //The effect file for blurring an image
        Effect blurEffect;

        //postprocessing rendertargets
        RenderTarget2D renderTarget1;
        RenderTarget2D renderTarget2;
        RenderTarget2D renderTarget3;
        Texture2D depthBuffer;
        Texture2D blurBuffer;
        Texture2D colorBuffer;

        // Offsets and wieghts for horizontal and vertical blurs
        Vector2[] sampleOffsetsH = new Vector2[15];
        float[] sampleWeightsH = new float[15];

        Vector2[] sampleOffsetsV = new Vector2[15];
        float[] sampleWeightsV = new float[15];

        // Direction of Gaussian Blur: Horizontal or Vertical
        public enum GaussianBlurDirection { Horizontal, Vertical };

        public DepthOfField(Camera _camera, int Width, int Height, GameScreen Parent)
            : base(Engine.Content.Load<Effect>("Content/Effects/DepthOfFieldEffect"), Width, Height, Parent) 
        {

            //load the blur effect file
            blurEffect = Engine.Content.Load<Effect>("Content/Effects/BlurEffect");
            camera = _camera;
            PresentationParameters pp = Engine.GraphicsDevice.PresentationParameters;
            //DepthMap RenderTarget
            renderTarget1 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, 1, pp.BackBufferFormat);
            //rt 2+3 is used to print blurred images on, these are half sized rendertargets
            renderTarget2 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth / 2, pp.BackBufferHeight / 2, 0, pp.BackBufferFormat);
            renderTarget3 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth / 2, pp.BackBufferHeight / 2, 0, pp.BackBufferFormat);
            // Set up the sampling parameters for the blur portion of the effect
            Vector2 texelSize = new Vector2(1f / Width, 1f / Height);
            SetBlurParameters(texelSize.X, 0, ref sampleOffsetsH, ref sampleWeightsH);
            SetBlurParameters(0, texelSize.Y, ref sampleOffsetsV, ref sampleWeightsV);
        }


        // Borrowed from "Shadow Mapping" by Andrew Joli
        // http://www.ziggyware.com/readarticle.php?article_id=161
        void SetBlurParameters(float dx, float dy, ref Vector2[] vSampleOffsets, ref float[] fSampleWeights)
        {
            // The first sample always has a zero offset.
            fSampleWeights[0] = ComputeGaussian(0);
            vSampleOffsets[0] = new Vector2(0);

            // Maintain a sum of all the weighting values.
            float totalWeights = fSampleWeights[0];

            // Add pairs of additional sample taps, positioned
            // along a line in both directions from the center.
            for (int i = 0; i < 15 / 2; i++)
            {
                // Store weights for the positive and negative taps.
                float weight = ComputeGaussian(i + 1);

                fSampleWeights[i * 2 + 1] = weight;
                fSampleWeights[i * 2 + 2] = weight;

                totalWeights += weight * 2;

                // The 1.5 offset kicks things off by
                // positioning us nicely in between two texels.
                float sampleOffset = i * 2 + 1.5f;

                Vector2 delta = new Vector2(dx, dy) * sampleOffset;

                // Store texture coordinate offsets for the positive and negative taps.
                vSampleOffsets[i * 2 + 1] = delta;
                vSampleOffsets[i * 2 + 2] = -delta;
            }

            // Normalize the list of sample weightings, so they will always sum to one.
            for (int i = 0; i < fSampleWeights.Length; i++)
                fSampleWeights[i] /= totalWeights;
        }

        // Borrowed from "Shadow Mapping" by Andrew Joli
        // http://www.ziggyware.com/readarticle.php?article_id=161
        private float ComputeGaussian(float n)
        {
            float theta = 2.0f + float.Epsilon;
            return theta = (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) * Math.Exp(-(n * n) / (2 * theta * theta)));
        }

        // Set blur parameters to effect
        void SetParameters(GaussianBlurDirection Direction)
        {
            if (Direction == GaussianBlurDirection.Horizontal)
            {
                blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsH);
                blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsH);
            }
            else
            {
                blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsV);
                blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsV);
            }
        }        

       
        public override void Draw()
        {

            //all 3d components that have just been rendered, are stored in our parent gamescreens DrawableRender list
            
            
            //the depth of field shader needs three rendered textures: a
            // - colorbuffer (our fully rendered scene)
            // - depthbuffer 
            // - blurbuffer
            // in the shader, the blurbuffer is blended with the depthbuffer and the color buffer

            //
            //**COLORBUFFER**//
            //
            //1) store the colorbuffer 
                // store last frame in base class Input texture
                GetInputFromFrameBuffer();
                // store the input texture as our colorbuffer
                colorBuffer = Input;
            //
            //**DEPTHBUFFER**//
            //

            DepthStencilBuffer DBuffer = new DepthStencilBuffer(Engine.GraphicsDevice,
                                        Engine.GraphicsDevice.Viewport.Width,
                                        Engine.GraphicsDevice.Viewport.Height,
                                        Engine.GraphicsDevice.DepthStencilBuffer.Format);

            //2) set fog settings for depthmap specs       
                Engine.GraphicsDevice.RenderState.FogEnable = true;
                Engine.GraphicsDevice.RenderState.FogStart = dofStart;
                Engine.GraphicsDevice.RenderState.FogDensity = 1.0f;
                Engine.GraphicsDevice.RenderState.FogEnd = dofStart + dofLength;
                Engine.GraphicsDevice.RenderState.FogTableMode = FogMode.Linear;
                Engine.GraphicsDevice.RenderState.FogColor = Color.White;

            //3)create a depthmap by drawing all components needed into rendertarget1
                Engine.GraphicsDevice.SetRenderTarget(0, renderTarget1);            

             
               
                //draw all models from our list that stored all last drawn models
                    foreach (Drawable3dComponent comp in this.Parent.DrawableRenderList)
                    {

                        //draw the objects using the depthmap shader
                        comp.DrawDepthMap();

                    } 

            //4)reset the rendertarget for future rendering
                Engine.GraphicsDevice.SetRenderTarget(0, null);

            //5)store the print as our depthbuffer               
                depthBuffer = renderTarget1.GetTexture();

            //
            //**BLURBUFFER HORIZONTAL**//
            //
           
            //6)set horizontal blur settings
                SetParameters(GaussianBlurDirection.Horizontal); // Set horizontal parameters

            //7)render the colorbuffer into rendertarget2 using the blur shader from the effect file
                DrawImageToRenderTarget(colorBuffer, renderTarget2, 0, 0, blurEffect, "Blur");

            //8)store the texture as the new blurbuffer
                blurBuffer = renderTarget2.GetTexture();

            //9)set vertical blur settings
                SetParameters(GaussianBlurDirection.Vertical); // Set horizontal parameters
            //10)render the horizontally blurred image into rendertarget3
                DrawImageToRenderTarget(blurBuffer, renderTarget3, 0, 0, blurEffect, "Blur");

            //11)store the texture as the new blurbuffer
                blurBuffer = renderTarget3.GetTexture();

            //
            //**BLURBUFFER HORIZONTAL**//
            //
            //12)final step, store the three buffers into our depth of field effect file, and make the draw call 
            //now for test purposes: store the blur texture as our input texture and have the base class render it
                Effect.Parameters["xColorTexture"].SetValue(colorBuffer);
                Effect.Parameters["xDepthTexture"].SetValue(depthBuffer);
                Effect.Parameters["xBlurTexture"].SetValue(blurBuffer);  
            
            
            
            Input = colorBuffer;

                
            //for direct output: render the depthmap into our Input texture and draw it through base class
            base.Draw();
        }


        //this function draws a requested texture into a determined rendertarget, using a requested effect
        private void DrawImageToRenderTarget(Texture2D texture, RenderTarget2D rendertarget, int offsetX, int offsetY, Effect effect, String technique)
        {
            Engine.GraphicsDevice.SetRenderTarget(0, rendertarget);
           
            effect.CurrentTechnique = effect.Techniques[technique];
            
            int width;
            int height;

            width = rendertarget.Width;
            height = rendertarget.Height;
            

            
                Engine.SpriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.SaveState);
               
                effect.Begin();
                effect.CurrentTechnique.Passes[0].Begin();
                

                Engine.SpriteBatch.Draw(texture, new Rectangle(offsetX, offsetY, width, height), Color.White);
                Engine.SpriteBatch.End();
               
                effect.CurrentTechnique.Passes[0].End();
                effect.End();
                
            

            Engine.GraphicsDevice.SetRenderTarget(0, null);
        }
      
 
        
    }
 
    
}